<script>
import {
  GlFormCheckbox,
  GlLink,
  GlSprintf,
  GlSkeletonLoader,
  GlTooltipDirective,
  GlTable,
} from '@gitlab/ui';
import { Portal } from 'portal-vue';
import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/shared/empty_states/dashboard_has_no_vulnerabilities.vue';
import FiltersProducedNoResults from 'ee/security_dashboard/components/shared/empty_states/filters_produced_no_results.vue';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import convertReportType from 'ee/vue_shared/security_reports/store/utils/convert_report_type';
import getPrimaryIdentifier from 'ee/vue_shared/security_reports/store/utils/get_primary_identifier';
import FalsePositiveBadge from 'ee/vulnerabilities/components/false_positive_badge.vue';
import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
import AutoFixHelpText from '../auto_fix_help_text.vue';
import IssuesBadge from '../issues_badge.vue';
import VulnerabilityCommentIcon from '../vulnerability_comment_icon.vue';
import VulnerabilityPath from './vulnerability_path.vue';
import SelectionSummary from './selection_summary.vue';
import VulnerabilityListStatus from './vulnerability_list_status.vue';
import { FIELDS } from './constants';

export default {
  components: {
    GlFormCheckbox,
    GlLink,
    GlSkeletonLoader,
    GlSprintf,
    GlTable,
    IssuesBadge,
    AutoFixHelpText,
    RemediatedBadge,
    FalsePositiveBadge,
    SelectionSummary,
    SeverityBadge,
    VulnerabilityCommentIcon,
    VulnerabilityPath,
    VulnerabilityListStatus,
    FiltersProducedNoResults,
    DashboardHasNoVulnerabilities,
    Portal,
  },
  directives: {
    GlTooltip: GlTooltipDirective,
  },
  inject: {
    hasVulnerabilities: {
      default: false,
    },
    canAdminVulnerability: {
      default: false,
    },
    hasJiraVulnerabilitiesIntegrationEnabled: {
      default: false,
    },
  },
  props: {
    fields: {
      type: Array,
      required: true,
    },
    vulnerabilities: {
      type: Array,
      required: true,
    },
    isLoading: {
      type: Boolean,
      required: false,
      default: false,
    },
    shouldShowProjectNamespace: {
      type: Boolean,
      required: false,
      default: false,
    },
    portalName: {
      type: String,
      required: true,
    },
    pageSize: {
      type: Number,
      required: true,
    },
    sort: {
      type: Object,
      required: false,
      default: () => ({
        sortBy: FIELDS.SEVERITY.key,
        sortDesc: true,
      }),
    },
  },
  data() {
    return {
      selectedVulnerabilities: {},
    };
  },
  computed: {
    displayFields() {
      // Add the checkbox field if the user can use the bulk select feature.
      return this.canAdminVulnerability ? [FIELDS.CHECKBOX, ...this.fields] : this.fields;
    },
    hasAnyScannersOtherThanGitLab() {
      return this.vulnerabilities.some(
        (v) => v.scanner?.vendor !== 'GitLab' && v.scanner?.vendor !== '',
      );
    },
    hasSelectedSomeVulnerabilities() {
      // Whether the user has selected at least 1, but not all vulnerabilities.
      return this.numOfSelectedVulnerabilities > 0 && !this.hasSelectedAllVulnerabilities;
    },
    hasSelectedAllVulnerabilities() {
      if (!this.vulnerabilities.length) {
        return false;
      }
      return this.numOfSelectedVulnerabilities === this.vulnerabilities.length;
    },
    numOfSelectedVulnerabilities() {
      return Object.keys(this.selectedVulnerabilities).length;
    },
    shouldShowSelectionSummary() {
      return this.numOfSelectedVulnerabilities > 0;
    },
  },
  watch: {
    vulnerabilities() {
      const ids = new Set(this.vulnerabilities.map((v) => v.id));

      Object.keys(this.selectedVulnerabilities).forEach((vulnerabilityId) => {
        if (!ids.has(vulnerabilityId)) {
          this.$delete(this.selectedVulnerabilities, vulnerabilityId);
        }
      });
    },
  },
  methods: {
    hasCluster(item) {
      return item.location.kubernetesResource?.agent;
    },
    clusterName(item) {
      return item.location.kubernetesResource?.agent?.name;
    },
    clusterUrl(item) {
      return item.location.kubernetesResource?.agent?.webPath;
    },
    deselectVulnerabilities(vulnerabilityIds) {
      vulnerabilityIds.forEach((id) => {
        this.$delete(this.selectedVulnerabilities, id);
      });
    },
    deselectAllVulnerabilities() {
      this.selectedVulnerabilities = {};
    },
    extraIdentifierCount(identifiers) {
      return identifiers.length - 1;
    },
    primaryIdentifier(identifiers) {
      return getPrimaryIdentifier(identifiers, 'externalType');
    },
    isSelected(vulnerability = {}) {
      return Boolean(this.selectedVulnerabilities[vulnerability.id]);
    },
    selectAllVulnerabilities() {
      this.selectedVulnerabilities = this.vulnerabilities.reduce((acc, curr) => {
        acc[curr.id] = curr;
        return acc;
      }, {});
    },
    shouldShowExtraIdentifierCount(identifiers) {
      return identifiers?.length > 1;
    },
    toggleAllVulnerabilities() {
      if (this.hasSelectedAllVulnerabilities) {
        this.deselectAllVulnerabilities();
      } else {
        this.selectAllVulnerabilities();
      }
    },
    toggleVulnerability(vulnerability) {
      // If the user can't use the bulk select feature (like the auditor user), don't do anything.
      if (!this.canAdminVulnerability) {
        return;
      }

      if (this.selectedVulnerabilities[vulnerability.id]) {
        this.$delete(this.selectedVulnerabilities, `${vulnerability.id}`);
      } else {
        this.$set(this.selectedVulnerabilities, `${vulnerability.id}`, vulnerability);
      }
    },
    gitlabIssues(item) {
      return item.issueLinks?.nodes || [];
    },
    externalIssues(item) {
      return item.externalIssueLinks?.nodes || [];
    },
    jiraIssues(item) {
      return this.externalIssues(item).filter(({ issue }) => issue?.externalTracker === 'jira');
    },
    badgeIssues(item) {
      return this.hasJiraVulnerabilitiesIntegrationEnabled
        ? this.jiraIssues(item)
        : this.gitlabIssues(item);
    },
    formatDate(item) {
      return formatDate(item.detectedAt, 'yyyy-mm-dd');
    },
    formatDateTooltip(item) {
      return formatDate(item.detectedAt);
    },
    hasComments(item) {
      return item.userNotesCount > 0;
    },
    useConvertReportType(reportType) {
      return convertReportType(reportType);
    },
    handleSortChange({ sortBy, sortDesc }) {
      this.$emit('update:sort', { sortBy, sortDesc });
    },
  },
};
</script>

<template>
  <div>
    <portal :to="portalName">
      <selection-summary
        :selected-vulnerabilities="Object.values(selectedVulnerabilities)"
        :visible="shouldShowSelectionSummary"
        @cancel-selection="deselectAllVulnerabilities"
        @vulnerabilities-updated="deselectVulnerabilities"
      />
    </portal>

    <gl-table
      class="vulnerability-list"
      :busy="isLoading"
      :fields="displayFields"
      :items="vulnerabilities"
      :thead-class="{ 'below-selection-summary': shouldShowSelectionSummary }"
      :sort-desc="sort.sortDesc"
      :sort-by="sort.sortBy"
      sort-direction="last"
      sort-icon-left
      no-local-sorting
      no-sort-reset
      stacked="sm"
      show-empty
      hover
      primary-key="id"
      :tbody-tr-class="{ 'gl-cursor-pointer': canAdminVulnerability }"
      head-variant="white"
      @sort-changed="handleSortChange"
      @row-clicked="toggleVulnerability"
    >
      <template #head(checkbox)>
        <gl-form-checkbox
          class="gl-display-table-cell"
          data-testid="vulnerability-checkbox-all"
          :checked="hasSelectedAllVulnerabilities"
          :indeterminate="hasSelectedSomeVulnerabilities"
          @change="toggleAllVulnerabilities"
        />
      </template>

      <template #cell(checkbox)="{ item }">
        <gl-form-checkbox
          class="gl-pointer-events-none gl-display-table-cell"
          data-testid="vulnerability-checkbox"
          :checked="isSelected(item)"
          @change="toggleVulnerability(item)"
        />
      </template>

      <template #cell(detected)="{ item }">
        <time v-gl-tooltip :data-testid="`detected-${item.id}`" :title="formatDateTooltip(item)">
          {{ formatDate(item) }}
        </time>
      </template>

      <template #cell(state)="{ item }">
        <vulnerability-list-status
          :state="item.state"
          :dismissal-reason="item.dismissalReason"
          :data-qa-status-description="item.title || item.name"
          data-testid="vulnerability-status-content"
        />
      </template>

      <template #cell(severity)="{ item }">
        <severity-badge class="js-severity" :severity="item.severity" />
      </template>

      <template #cell(title)="{ item }">
        <div
          class="gl-display-flex gl-flex-direction-column flex-sm-row gl-align-items-end align-items-sm-center"
          :data-testid="`title-${item.id}`"
        >
          <gl-link
            class="gl-text-body vulnerability-title js-description"
            :href="item.vulnerabilityPath"
            :data-qa-vulnerability-description="item.title || item.name"
            data-testid="vulnerability"
            @click="$emit('vulnerability-clicked', item)"
          >
            {{ item.title || item.name }}
          </gl-link>
          <vulnerability-comment-icon v-if="hasComments(item)" :vulnerability="item" />
        </div>
        <div
          v-if="item.location"
          :data-testid="`location-${item.id}`"
          class="gl-text-color-secondary gl-font-sm"
        >
          <div v-if="shouldShowProjectNamespace">
            {{ item.project.nameWithNamespace }}
          </div>
          <vulnerability-path :location="item.location" />
        </div>
      </template>

      <template #cell(identifier)="{ item }">
        <div data-testid="vulnerability-identifier">
          {{ primaryIdentifier(item.identifiers) }}
        </div>
        <div
          v-if="shouldShowExtraIdentifierCount(item.identifiers)"
          data-testid="vulnerability-more-identifiers"
          class="gl-text-gray-300"
        >
          <gl-sprintf :message="__('+ %{count} more')">
            <template #count>
              {{ extraIdentifierCount(item.identifiers) }}
            </template>
          </gl-sprintf>
        </div>
      </template>

      <template #cell(reportType)="{ item }">
        <div
          data-testid="vulnerability-report-type"
          :class="{ 'text-capitalize': item.reportType !== 'GENERIC' }"
        >
          {{ useConvertReportType(item.reportType) }}
        </div>
        <div
          v-if="hasAnyScannersOtherThanGitLab && item.scanner"
          data-testid="vulnerability-vendor"
          class="gl-text-gray-300"
        >
          {{ item.scanner.vendor }}
        </div>
      </template>

      <template #cell(cluster)="{ item }">
        <gl-link
          v-if="hasCluster(item)"
          :data-testid="`cluster-${item.id}`"
          :href="clusterUrl(item)"
        >
          {{ clusterName(item) }}
        </gl-link>
      </template>

      <template #cell(activity)="{ item }">
        <div class="gl-display-flex gl-justify-content-end">
          <auto-fix-help-text v-if="item.mergeRequest" :merge-request="item.mergeRequest" />
          <issues-badge
            v-if="badgeIssues(item).length > 0"
            :issues="badgeIssues(item)"
            :is-jira="hasJiraVulnerabilitiesIntegrationEnabled"
            data-testid="vulnerability-issue-created-badge-content"
            :data-qa-badge-description="item.title || item.name"
          />
          <false-positive-badge
            v-if="item.falsePositive"
            data-testid="false-positive-vulnerability"
            class="gl-ml-3"
          />
          <remediated-badge
            v-if="item.resolvedOnDefaultBranch"
            class="gl-ml-3"
            data-testid="vulnerability-remediated-badge-content"
            :data-qa-activity-description="item.title || item.name"
          />
        </div>
      </template>

      <template #table-busy>
        <gl-skeleton-loader v-for="n in pageSize" :key="n" :lines="2" />
      </template>

      <template #empty>
        <filters-produced-no-results v-if="hasVulnerabilities" class="gl-cursor-default" />
        <dashboard-has-no-vulnerabilities v-else class="gl-cursor-default" />
      </template>
    </gl-table>
  </div>
</template>
